3. Models for Predicting Estimated Time of Arrival#

3.1 Machine Learning Models 개요#

3.1.1 머신러닝을 공부하기 위한 교재 추천#

본 장에서는 ETA(통행시간예측) 모델을 만들기 위한 기초적인 머신러닝 이론을 공부한다.
기초적인 지식을 위해 아래의 교재를 공부하기를 추천한다.

해당 교재는 머신러닝의 기초부터, 딥러닝까지 폭넓게 다루고 있으며 각 챕터마다의 상세한 예제 코드 파일을 함께 제공 하고 있어, 초심자가 공부하기에 적합하다.
하지만, 본 교재에서 모두 다루기에는 양이 방대하므로, 머신러닝에 집중해서 공부를 하는 것을 추천하며, 그 중에서도 1장, 2장, 3장, 6장, 7장까지 공부하여 Supervised Learning의 개념을 이해하고, 그 중에서도 가장 널리 쓰이는 Ensemble Model에 대해 이해하는 것이 중요하다.

3.1.2 도시/교통 분야에서의 머신러닝 응용사례#

도시와 교통 분야에서 머신러닝 기술은 다양한 문제 해결과 의사결정 지원에 활용되고 있다. 지도학습과 비지도학습 기법을 통해 복잡한 도시 시스템을 분석하고 최적화하는 데 큰 기여를 하고 있다. 본 절에서는 주요 응용사례를 살펴본다.

지도학습 (Supervised Learning)#

지도학습은 레이블이 지정된 데이터를 사용하여 입력과 출력 사이의 관계를 학습하는 머신러닝 기법이다. 이 방법은 과거의 데이터와 그에 대응하는 결과(레이블)를 바탕으로 모델을 훈련시켜, 새로운 데이터에 대한 예측이나 분류를 수행한다. 지도학습의 주요 목적은 입력 데이터로부터 정확한 출력을 예측하는 함수를 찾는 것이다. 도시 및 교통 분야에서 지도학습은 주로 다음과 같은 목적으로 활용된다:

  • 미래의 상황이나 수치를 예측하는 회귀(Regression) 분석

  • 데이터를 특정 범주로 분류하는 분류(Classification) 작업

  • 시계열 데이터를 분석하여 미래 트렌드를 예측하는 작업

이러한 기법들은 도시 계획, 교통 관리, 안전 시스템 등 다양한 영역에서 중요한 역할을 한다. 다음은 도시/교통 분야에서의 대표적인 지도학습 응용 사례들이다:

  • 통행수요 예측: 통행수요 예측은 특정 지역이나 노선의 미래 통행량을 예측하는 모델이다. 이 모델은 과거 통행 데이터, 인구통계, 경제지표, 날씨 등의 특성을 입력으로 사용한다. 예측 결과는 대중교통 노선 계획이나 도로 확장 계획 수립 등에 활용된다.

  • 통행시간 예측: 출발지에서 목적지까지의 예상 소요시간을 예측하는 모델이다. 실시간 교통 데이터, 과거 통행시간 패턴, 도로 상태 등을 고려하여 예측을 수행한다. 이는 네비게이션 서비스나 교통정보 제공 시스템에서 중요한 역할을 한다.

  • 통행속도 예측: 특정 도로나 구간의 미래 통행속도를 예측하는 모델이다. 현재 교통량, 시간대, 날씨 조건 등을 고려하여 예측을 수행한다. 예측 결과는 교통 흐름 최적화나 신호 제어 시스템 등에 활용된다.

  • 교통사고 발생(사고위험도) 예측: 특정 지역이나 도로에서의 교통사고 발생 가능성을 예측하는 모델이다. 도로 설계, 교통량, 날씨, 시간대 등의 요소를 고려하여 위험도를 산출한다. 이는 교통안전 정책 수립이나 위험 구간 개선 등에 중요한 정보를 제공한다.

  • 주차수요 예측: 특정 지역이나 시설의 주차 수요를 예측하는 모델이다. 시간대, 요일, 주변 이벤트 등을 고려하여 예측을 수행한다. 예측 결과는 주차 관리 시스템이나 스마트 주차 솔루션 등에 활용된다.

  • 대중교통 승하차 인원 예측: 버스나 지하철 역의 시간대별 승하차 인원을 예측하는 모델이다. 과거 데이터, 주변 환경, 이벤트 정보 등을 활용하여 예측을 수행한다. 이는 대중교통 운행 계획 수립이나 혼잡도 관리 등에 중요한 정보를 제공한다.

비지도 학습 (Unsupervised Learning)#

비지도 학습은 레이블이 지정되지 않은 데이터에서 패턴이나 구조를 찾아내는 머신러닝 기법이다. 이는 주로 데이터의 숨겨진 구조를 발견하거나, 데이터를 의미 있는 방식으로 그룹화하는 데 사용된다. 비지도 학습의 주요 특징은 사전에 정의된 ‘정답’이 없다는 점이다. 대신, 알고리즘이 데이터 자체의 특성을 바탕으로 패턴을 찾아낸다. 도시 및 교통 분야에서 비지도 학습은 주로 다음과 같은 목적으로 활용된다

  • 유사한 특성을 가진 데이터 포인트들을 그룹화하는 군집화

  • 정상 패턴에서 벗어난 데이터를 감지하는 이상치 탐지

  • 복잡한 고차원 데이터를 더 단순한 저차원으로 변환하는 차원 축소

이러한 기법들은 도시와 교통 시스템의 복잡한 패턴을 이해하고, 효율적인 관리 및 계획 수립에 중요한 인사이트를 제공한다. 다음은 도시/교통 분야에서의 대표적인 비지도 학습 응용 사례들이다:

  • 교통 패턴 군집화: 유사한 교통 패턴을 가진 지역이나 시간대를 그룹화하는 기법이다. K-means, DBSCAN 등의 알고리즘을 주로 사용한다. 군집화 결과는 교통 정책 수립이나 신호 체계 최적화 등에 활용된다.

  • 이상 교통 상황 탐지: 평소와 다른 비정상적인 교통 패턴을 자동으로 감지하는 기법이다. 이상치 탐지 알고리즘을 사용하여 분석을 수행한다. 이는 교통사고, 도로 공사, 대규모 행사 등의 영향을 빠르게 파악하는 데 사용된다.

  • 도시 기능 지역 분류: 도시 내 다양한 지역의 기능(주거, 상업, 업무 등)을 자동으로 분류하는 기법이다. 토지 이용 패턴, 건물 형태, 유동인구 데이터 등을 활용하여 분석을 수행한다. 분류 결과는 도시계획이나 토지이용 정책 수립 등에 활용된다.

  • 차량 주행 패턴 분석: 차량의 주행 데이터를 분석하여 유사한 주행 패턴을 찾아내는 기법이다. GPS 데이터, 속도 데이터 등을 활용하여 분석을 수행한다. 분석 결과는 친환경 운전 방식 개발이나 차량 설계 최적화 등에 활용된다. 예를들어 버스/택시기사들의 운행기록 데이터 정보가 있다면, 유사하게 운전하는 운전자들끼리 군집화가 가능할 것이며, 이 군집화를 기반으로 운전자들의 성향을 파악하는 것이 가능하다.

이러한 머신러닝 기법들은 도시와 교통 시스템을 더욱 효율적이고 지속가능하게 만드는 데 중요한 역할을 한다. 실제 적용 시에는 데이터의 품질, 모델의 정확성, 윤리적 고려사항 등 다양한 요소를 종합적으로 고려해야 한다. 도시/교통 분야의 머신러닝 응용에 대한 실제 구현 사례와 코드를 살펴보고 싶다면, GitHub에서 제공하는 ‘Traffic-Prediction-Open-Code-Summary’ 리포지토리를 참고하는 것이 도움이 될 수 있다. 이 리소스는 다양한 교통 예측 모델의 오픈 소스 코드를 제공하고 있어, 실제 구현 방법을 이해하는 데 도움을 준다. 이밖에도 Kaggle, 데이콘, 콤파스 등에 다양한 도시/교통 관련된 머신러닝 문제들과 데이터가 있으니 도전해보면 좋다.

3.2 ETA (Estimated Time of Arrival) Models (O-D based Travel Time Prediction)#

3.2.1 Problem Definitions for Predicting Estimated Time of Arrival#

도착 예정 시간(ETA, Estimated Time of Arrival) 예측은 교통 및 물류 분야에서 중요한 과제이다. 이 작업의 목표는 차량이 출발지에서 목적지까지 이동하는 데 걸리는 시간을 정확하게 예측하는 것이다. 이 예측은 이동 시간에 영향을 미칠 수 있는 다양한 요인들을 기반으로 한다.

ETA 예측 문제는 다음과 같이 공식적으로 정의할 수 있다:

입력 특성 집합 \(X = \{x_1, x_2, ..., x_n\}\)이 주어졌을 때, 각 \(x_i\)는 다음과 같은 관련 요인을 나타낸다:

  • 출발지와 목적지의 위도 및 경도 좌표

  • 출발지와 목적지 사이의 직선 거리

  • 출발 시간

  • 요일

  • 공휴일 여부

  • 출발지와 목적지의 행정 구역

목표는 이러한 입력 특성을 사용하여 예상 도착 시간 \(y\)를 예측하는 함수 \(f\)를 학습하는 것이다:

\(y = f(X)\)

여기서 \(y\)는 분 단위의 예상 이동 시간을 나타낸다.

3.2.2 Data Preparation#

이 단계에서는 ETA 예측을 위한 데이터를 수집하고 정리한다. 주요 작업으로는 다양한 소스에서 데이터를 수집하고, 데이터의 일관성을 확인하며, 누락된 값이나 이상치를 처리하는 것이 포함된다. 또한 데이터의 형식을 통일하고, 필요한 경우 데이터를 결합하는 작업도 수행한다.

import pandas as pd
import numpy as np
import osmnx as ox
import gdown
import os
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[1], line 1
----> 1 import pandas as pd
      2 import numpy as np
      3 import osmnx as ox

ModuleNotFoundError: No module named 'pandas'
def download_and_read_parquet(file_id, output_path="../data/chp3_tx_data_ml.parquet"):
    try:
        # Google Drive에서 파일 다운로드
        gdown.download(id=file_id, output=output_path, quiet=False)
        
        # Parquet 파일을 DataFrame으로 읽기
        df = pd.read_parquet(output_path)
        
        # 임시 파일 삭제 (데이터 용량이 매우 큰 경우 사용)
        os.remove(output_path) # 다운로드 받은 데이터를 삭제하고 싶지 않을 때는 해당 라인을 주석처리
        
        return df
    except Exception as e:
        print(f"오류 발생: {e}")
        return None
# 파일 불러오기
file_id = "1-j6lY58m_gsvO5KushZGcvQtg8rwN8Bq" # 구글 드라이브에 업로드 된 파일의 ID
df = download_and_read_parquet(file_id)
Downloading...
From (original): https://drive.google.com/uc?id=1-j6lY58m_gsvO5KushZGcvQtg8rwN8Bq
From (redirected): https://drive.google.com/uc?id=1-j6lY58m_gsvO5KushZGcvQtg8rwN8Bq&confirm=t&uuid=68d21af3-8cc9-49a7-a80b-688b91c30b7c
To: e:\book\urban-mobility-simulation\data\chp3_tx_data_ml.parquet
100%|██████████| 201M/201M [00:17<00:00, 11.5MB/s] 
# RIDE_DTIME을 datetime으로 변환
df['RIDE_DTIME'] = pd.to_datetime(df['RIDE_DTIME'], format='%Y%m%d%H%M%S')
df['ALIGHT_DTIME'] = pd.to_datetime(df['ALIGHT_DTIME'], format='%Y%m%d%H%M%S')
# target
df['TRAVEL_TIME'] = (df['ALIGHT_DTIME'] - df['RIDE_DTIME']) / pd.Timedelta(minutes=1)
df
RIDE_DTIME RIDE_POS_X RIDE_POS_Y ALIGHT_DTIME ALIGHT_POS_X ALIGHT_POS_Y TRAVEL_TIME
0 2022-05-21 11:01:23 127.028467 37.585553 2022-05-21 11:17:41 127.022692 37.612146 16.300000
1 2022-05-29 03:39:50 126.928486 37.486230 2022-05-29 03:54:18 126.875179 37.466892 14.466667
2 2022-05-17 23:18:19 127.029616 37.499293 2022-05-17 23:35:26 127.117172 37.499710 17.116667
3 2022-05-05 23:19:29 126.973678 37.580075 2022-05-05 23:26:31 126.975649 37.552622 7.033333
4 2022-05-28 05:00:48 126.921493 37.555215 2022-05-28 05:14:48 126.859654 37.547189 14.000000
... ... ... ... ... ... ... ...
4548922 2022-05-14 21:16:18 126.890544 37.506478 2022-05-14 21:31:29 126.855718 37.503107 15.183333
4548923 2022-05-20 06:54:45 127.042237 37.662474 2022-05-20 06:58:22 127.034707 37.648935 3.616667
4548924 2022-05-12 05:11:36 126.851621 37.508182 2022-05-12 05:27:07 126.904974 37.512342 15.516667
4548925 2022-05-24 13:03:07 126.979372 37.548261 2022-05-24 13:19:39 126.941519 37.561741 16.533333
4548926 2022-05-15 18:07:40 126.973236 37.535928 2022-05-15 18:14:15 127.001178 37.537362 6.583333

4548927 rows × 7 columns

3.2.3 EDA (Exploratory Data Analysis)#

탐색적 데이터 분석 단계에서는 수집된 데이터의 특성과 패턴을 파악한다. 여기에는 각 변수의 분포 확인, 변수 간 상관관계 분석, 시간에 따른 추세 파악 등이 포함된다. 또한 이상치나 특이점을 식별하고, 데이터의 품질을 평가하는 작업도 수행한다. 이 과정을 통해 모델 구축에 유용한 인사이트를 얻을 수 있다.

[1] 시간 변수 가공#

  • RIDE_DTIME을 기준으로, 요일, 시간, 휴일여부 등을 매핑

# 요일 추출 (0: 월요일, 6: 일요일)
df['DAY_OF_WEEK'] = df['RIDE_DTIME'].dt.dayofweek

# 시간 추출
df['HOUR'] = df['RIDE_DTIME'].dt.hour

# 주말 여부 (1: 주말, 0: 평일)
df['IS_WEEKEND'] = df['DAY_OF_WEEK'].isin([5, 6]).astype(int)

# 공휴일 여부 (예시로 몇 개의 공휴일만 포함)
holidays = ['2022-01-01', '2022-03-01', '2022-05-05', '2022-08-15', '2022-10-03']
df['IS_HOLIDAY'] = df['RIDE_DTIME'].dt.strftime('%Y-%m-%d').isin(holidays).astype(int)

[2] 결측치 확인#

  • 데이터의 결측치를 확인한다.

  • 향후 데이터 전처리 파이프라인 구축할 때 결측치에 대해 처리할 수 있다.

# 결측치 확인
missing_count = df.isnull().sum()
missing_percentage = 100 * df.isnull().sum() / len(df)

missing_info = pd.concat([missing_count, missing_percentage], axis=1, keys=['Missing Count', 'Missing Percentage(%)'])
print(missing_info)

# 결측치가 있는 열만 표시
columns_with_missing = missing_info[missing_info['Missing Count'] > 0]
if not columns_with_missing.empty:
    print("\nColumns with missing values:")
    print(columns_with_missing)
else:
    print("\nNo missing values in any column.")
              Missing Count  Missing Percentage(%)
RIDE_DTIME                0                    0.0
RIDE_POS_X                0                    0.0
RIDE_POS_Y                0                    0.0
ALIGHT_DTIME              0                    0.0
ALIGHT_POS_X              0                    0.0
ALIGHT_POS_Y              0                    0.0
TRAVEL_TIME               0                    0.0
DAY_OF_WEEK               0                    0.0
HOUR                      0                    0.0
IS_WEEKEND                0                    0.0
IS_HOLIDAY                0                    0.0

No missing values in any column.

[3] 입력변수들의 분포 및 Target 변수와의 관계 분석#

df.describe()
RIDE_DTIME RIDE_POS_X RIDE_POS_Y ALIGHT_DTIME ALIGHT_POS_X ALIGHT_POS_Y TRAVEL_TIME DAY_OF_WEEK HOUR IS_WEEKEND IS_HOLIDAY
count 4548927 4.548927e+06 4.548927e+06 4548927 4.548927e+06 4.548927e+06 4.548927e+06 4.548927e+06 4.548927e+06 4.548927e+06 4.548927e+06
mean 2022-05-16 17:19:02.734734592 1.269980e+02 3.754447e+01 2022-05-16 17:35:58.240318720 1.269958e+02 3.754313e+01 1.692509e+01 2.905811e+00 1.258344e+01 2.752887e-01 3.032561e-02
min 2022-05-01 00:00:00 1.267683e+02 3.742966e+01 2022-05-01 00:01:09 1.231249e+02 3.274790e+01 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00
25% 2022-05-09 06:59:27 1.269269e+02 3.750707e+01 2022-05-09 07:17:55.500000 1.269245e+02 3.750529e+01 7.833333e+00 1.000000e+00 8.000000e+00 0.000000e+00 0.000000e+00
50% 2022-05-16 19:45:01 1.270146e+02 3.753917e+01 2022-05-16 19:59:45 1.270137e+02 3.753957e+01 1.360000e+01 3.000000e+00 1.300000e+01 0.000000e+00 0.000000e+00
75% 2022-05-24 11:35:28 1.270589e+02 3.757095e+01 2022-05-24 11:51:39.500000 1.270622e+02 3.757587e+01 2.265000e+01 5.000000e+00 1.800000e+01 1.000000e+00 0.000000e+00
max 2022-05-31 23:59:59 1.271836e+02 3.769519e+01 2022-06-01 02:48:30 1.321688e+02 4.343108e+01 3.595333e+02 6.000000e+00 2.300000e+01 1.000000e+00 1.000000e+00
std NaN 8.183963e-02 4.747806e-02 NaN 9.615875e-02 6.345347e-02 1.272693e+01 2.022310e+00 6.842493e+00 4.466597e-01 1.714817e-01
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import math

# 범주형 변수 컬럼 추출
categorical_columns = ['DAY_OF_WEEK', 'HOUR', 'IS_WEEKEND', 'IS_HOLIDAY']

df_categorical = df[categorical_columns]
print(df_categorical.shape)

# Subplot 그리는 함수 생성
def make_subplot_layout(df, col_num):
    k = len(df.columns)
    row_num = math.ceil(k / col_num)
    plt.figure(figsize=(col_num * 4, row_num * 3))
    for i in range(k):
        plt.subplot(row_num, col_num, i + 1)
        sns.countplot(data=df, x=df.columns[i])
        plt.title(f'{df.columns[i]} Countplot')
        plt.xticks(rotation=45)
    plt.tight_layout()
    plt.show()

# 범주형 변수 시각화
make_subplot_layout(df_categorical, 2)
(4548927, 4)
../_images/2402c5776104aa41f06a9bf803c0469b76682b64e259b99fb12e97607214ff2c.png
# 수치형 변수 컬럼 추출
numeric_columns = ['RIDE_POS_X', 'RIDE_POS_Y', 'ALIGHT_POS_X', 'ALIGHT_POS_Y', 'TRAVEL_TIME']

# 수치형 변수의 분포 시각화
plt.figure(figsize=(15, 5 * math.ceil(len(numeric_columns)/3)))
for i, col in enumerate(numeric_columns, 1):
    plt.subplot(math.ceil(len(numeric_columns)/3), 3, i)
    sns.histplot(df[col], bins=50, kde=False)
    plt.title(f'{col} Distribution')
plt.tight_layout()
plt.show()
../_images/ad0b3c5fbe7ac21d65391f1bcdbcc75c437f56ea580fef64dcb3cbe875e967d9.png
# TRAVEL_TIME과 다른 변수들의 관계 시각화
plt.figure(figsize=(15, 5 * math.ceil((len(numeric_columns) + len(categorical_columns) - 1)/3)))
for i, col in enumerate(numeric_columns[:-1] + categorical_columns, 1):
    if col != 'TRAVEL_TIME':
        plt.subplot(math.ceil((len(numeric_columns) + len(categorical_columns) - 1)/3), 3, i)
        if col in numeric_columns[:-1]:
            sns.scatterplot(data=df, x=col, y='TRAVEL_TIME', alpha=0.1)
        else:
            sns.boxplot(data=df, x=col, y='TRAVEL_TIME', showfliers=False)
        plt.title(f'{col} vs TRAVEL_TIME')
plt.tight_layout()
plt.show()
../_images/4285137b55379eea4b4a7827674cf1b1a8ac6c6200846575998a56cfcdafe461.png

[4] 공간 시각화#

import pydeck as pdk

# 데이터 준비 (df는 이미 로드된 데이터프레임이라고 가정)
df_sample = df.sample(n=100000)  # 성능을 위해 샘플링

# 승차 위치 데이터
ride_data = df_sample[['RIDE_POS_X', 'RIDE_POS_Y']].rename(columns={'RIDE_POS_X': 'lon', 'RIDE_POS_Y': 'lat'})

# 하차 위치 데이터
alight_data = df_sample[['ALIGHT_POS_X', 'ALIGHT_POS_Y']].rename(columns={'ALIGHT_POS_X': 'lon', 'ALIGHT_POS_Y': 'lat'})
# 승차 위치 시각화 함수
def visualize_hexagon(data, layer_id):
    center_lat = data['lat'].mean()
    center_lon = data['lon'].mean()

    layer = pdk.Layer(
        'HexagonLayer',
        data=data,
        get_position=['lon', 'lat'],
        radius=200,
        elevation_scale=4,
        elevation_range=[0, 1000],
        pickable=True,
        extruded=True,
        opacity=0.8,
        id=layer_id
    )

    view_state = pdk.ViewState(
        latitude=center_lat,
        longitude=center_lon,
        zoom=10,
        pitch=50,
    )

    tooltip = {
        "html": "<b>개수:</b> {elevationValue}",
        "style": {"background": "grey", "color": "white", "font-family": '"Helvetica Neue", Arial', "z-index": "10000"}
    }

    legend = pdk.Layer(
        "TextLayer",
        data=[{"position": [center_lon, center_lat], "text": "택시 승하차 밀집도"}],
        get_position="position",
        get_text="text",
        get_size=18,
        get_color=[255, 255, 255],
        get_alignment_baseline="'bottom'",
    )

    r = pdk.Deck(
        layers=[layer, legend],
        initial_view_state=view_state,
        tooltip=tooltip,
    )

    return r
# 승차 위치 시각화
ride_visualization = visualize_hexagon(ride_data, 'ride_layer')
ride_visualization.to_html("../outputs/taxi_ride_map.html")
# 하차 위치 시각화
alight_visualization = visualize_hexagon(alight_data, 'alight_layer')
alight_visualization.to_html("../outputs/taxi_alight_map.html")

[5] 공간 시각화 기반의 데이터 처리#

  • 위와 같은 승차위치와 하차위치의 공간분포 차이는 왜 발생할까요?

  • 우리가 만드는 통행시간 예측 머신러닝 모델을 만들때 주의해야 할 부분이 있을까요?

### 서울 bbox 안에 있지 않은 ride, alight은 제거
place_inf = ox.geocode_to_gdf(['서울 대한민국'])

## - 서울 bbox (lat, lon)
# latitude
lat = place_inf[['bbox_south', 'bbox_north']].values[0].tolist()
# longitute
lon = place_inf[['bbox_west', 'bbox_east']].values[0].tolist()

df_filtered = df.loc[(df['RIDE_POS_Y'] >= lat[0]) & (df['RIDE_POS_Y'] <= lat[1]) & 
                            (df['RIDE_POS_X'] >= lon[0]) & (df['RIDE_POS_X'] <= lon[1])].reset_index(drop=True)

df_filtered = df_filtered.loc[(df['ALIGHT_POS_Y'] >= lat[0]) & (df_filtered['ALIGHT_POS_Y'] <= lat[1]) & 
                            (df_filtered['ALIGHT_POS_X'] >= lon[0]) & (df_filtered['ALIGHT_POS_X'] <= lon[1])].reset_index(drop=True)
df_filtered.shape
(4332957, 11)
sns.histplot(df_filtered['ALIGHT_POS_X'], bins=50, kde=False)
<Axes: xlabel='ALIGHT_POS_X', ylabel='Count'>
../_images/ed4a70431473cf0991cdcb58922f3005cc2f052298af06ccf891e40f174015b5.png
sns.histplot(df_filtered['ALIGHT_POS_Y'], bins=50, kde=False)
<Axes: xlabel='ALIGHT_POS_Y', ylabel='Count'>
../_images/2392c88792b9b0f89d6deb76f39c0668899f725fd2084918071d07b214c9d9b8.png
# 데이터 준비 (df는 이미 로드된 데이터프레임이라고 가정)
df_sample = df_filtered.sample(n=100000)  # 성능을 위해 샘플링

# 하차 위치 데이터
alight_data = df_sample[['ALIGHT_POS_X', 'ALIGHT_POS_Y']].rename(columns={'ALIGHT_POS_X': 'lon', 'ALIGHT_POS_Y': 'lat'})

# visualize_hexagon(alight_data, 'alight_layer')

3.2.4 Data Preprocessing (파이프라인 구축)#

전처리 단계에서는 원시 데이터를 모델 학습에 적합한 형태로 변환한다. 이 과정에는 특성 스케일링, 범주형 변수의 인코딩, 결측치 처리 등이 포함된다. 또한 효율적이고 일관된 데이터 처리를 위해 전처리 파이프라인을 구축한다.
이 파이프라인은 향후 새로운 데이터에 대해서도 동일한 전처리를 적용할 수 있다.

# 데이터 전처리 파이프라인 구축
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split
#  train_test_split 수행
X = df_filtered.drop('TRAVEL_TIME', axis=1)
y = df_filtered['TRAVEL_TIME']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 수치형 변수와 범주형 변수 구분
numeric_features = ['RIDE_POS_X', 'RIDE_POS_Y', 'ALIGHT_POS_X', 'ALIGHT_POS_Y']
categorical_features = ['DAY_OF_WEEK', 'HOUR', 'IS_WEEKEND', 'IS_HOLIDAY']

# 수치형 변수 전처리 파이프라인
numeric_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler())
])

# 범주형 변수 전처리 파이프라인
categorical_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='most_frequent')),
    ('onehot', OneHotEncoder(handle_unknown='ignore'))
])

# 전체 전처리 파이프라인
preprocessor = ColumnTransformer(
    transformers=[
        ('num', numeric_transformer, numeric_features),
        ('cat', categorical_transformer, categorical_features)
    ])

# 필요한 특성만 선택
X_train = X_train[numeric_features + categorical_features]
X_test = X_test[numeric_features + categorical_features]

# 전처리 파이프라인 적용
# train set에 fit_transform 적용
X_train_preprocessed = preprocessor.fit_transform(X_train)

# test set에는 transform만 적용
X_test_preprocessed = preprocessor.transform(X_test)

print("전처리 완료")
전처리 완료
# 전처리된 특성 이름 가져오기
feature_names = preprocessor.get_feature_names_out()

# 특성 이름 출력
print(feature_names)

# 특성의 총 개수 출력
print(f"전체 특성 개수: {len(feature_names)}")
['num__RIDE_POS_X' 'num__RIDE_POS_Y' 'num__ALIGHT_POS_X'
 'num__ALIGHT_POS_Y' 'cat__DAY_OF_WEEK_0' 'cat__DAY_OF_WEEK_1'
 'cat__DAY_OF_WEEK_2' 'cat__DAY_OF_WEEK_3' 'cat__DAY_OF_WEEK_4'
 'cat__DAY_OF_WEEK_5' 'cat__DAY_OF_WEEK_6' 'cat__HOUR_0' 'cat__HOUR_1'
 'cat__HOUR_2' 'cat__HOUR_3' 'cat__HOUR_4' 'cat__HOUR_5' 'cat__HOUR_6'
 'cat__HOUR_7' 'cat__HOUR_8' 'cat__HOUR_9' 'cat__HOUR_10' 'cat__HOUR_11'
 'cat__HOUR_12' 'cat__HOUR_13' 'cat__HOUR_14' 'cat__HOUR_15'
 'cat__HOUR_16' 'cat__HOUR_17' 'cat__HOUR_18' 'cat__HOUR_19'
 'cat__HOUR_20' 'cat__HOUR_21' 'cat__HOUR_22' 'cat__HOUR_23'
 'cat__IS_WEEKEND_0' 'cat__IS_WEEKEND_1' 'cat__IS_HOLIDAY_0'
 'cat__IS_HOLIDAY_1']
전체 특성 개수: 39

3.2.5 Construct Model#

모델 구축 단계에서는 ETA 예측을 위한 적절한 머신러닝 또는 딥러닝 모델을 선택하고 구현한다. 다양한 알고리즘을 비교하고, 모델의 구조와 하이퍼파라미터를 최적화하는 작업이 포함된다. 또한 모델의 성능을 향상시키기 위한 앙상블 기법이나 고급 학습 방법을 적용할 수 있다.

# LightGBM을 사용해서 머신러닝 모델 학습
import lightgbm as lgb
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint, uniform
from sklearn.metrics import mean_squared_error
import numpy as np
# LightGBM 모델 정의
lgb_model = lgb.LGBMRegressor(random_state=42)

# 하이퍼파라미터 탐색 공간 정의 (범위 축소)
param_distributions = {
    'num_leaves': randint(20, 100),      # 트리의 최대 잎 노드 수. 더 많은 잎은 더 복잡한 트리를 의미함
    'learning_rate': uniform(0.01, 0.1), # 학습률. 각 트리의 기여도를 조절. 작을수록 과적합 위험은 줄지만 학습에 더 많은 트리가 필요
    'n_estimators': randint(100, 300),   # 생성할 트리의 수. 더 많은 트리는 더 나은 성능을 낼 수 있지만 학습 시간이 증가함
    'max_depth': randint(3, 8)           # 트리의 최대 깊이. 깊을수록 복잡한 관계를 학습할 수 있지만 과적합 위험이 증가함
}

# RandomizedSearchCV 정의 (iteration 횟수 감소)
random_search = RandomizedSearchCV(
    lgb_model, 
    param_distributions=param_distributions,
    n_iter=10,
    cv=3,
    scoring='neg_mean_squared_error',  # RMSE를 최소화하기 위해 MSE 사용
    random_state=42,
    n_jobs=-1,
    verbose=1
)

# 모델 학습
random_search.fit(X_train_preprocessed, y_train)

# 최적 모델 저장
best_model = random_search.best_estimator_

# 최적 파라미터 출력
print("최적 파라미터:")
for param, value in random_search.best_params_.items():
    print(f"{param}: {value}")

# 최고 성능 출력 (RMSE로 변환)
best_score = np.sqrt(-random_search.best_score_)
print(f"최고 교차 검증 RMSE: {best_score:.4f}")
Fitting 3 folds for each of 10 candidates, totalling 30 fits
[LightGBM] [Info] Auto-choosing row-wise multi-threading, the overhead of testing was 0.036468 seconds.
You can set `force_row_wise=true` to remove the overhead.
And if memory is not enough, you can set `force_col_wise=true`.
[LightGBM] [Info] Total Bins 1090
[LightGBM] [Info] Number of data points in the train set: 3466365, number of used features: 39
[LightGBM] [Info] Start training from score 16.041805
최적 파라미터:
learning_rate: 0.09661761457749352
max_depth: 6
n_estimators: 203
num_leaves: 43
최고 교차 검증 RMSE: 6.4892

3.2.6 Validation#

검증 단계에서는 구축된 모델의 성능을 평가한다. 교차 검증이나 홀드아웃 검증과 같은 기법을 사용하여 모델의 일반화 능력을 측정한다. 평가 지표로는 평균 절대 오차(MAE), 평균 제곱근 오차(RMSE) 등을 사용할 수 있다. 또한 모델의 예측 결과를 실제 값과 비교하여 모델의 강점과 약점을 분석한다.

# 테스트 세트에서 성능 평가
test_pred = best_model.predict(X_test_preprocessed)
test_mse = mean_squared_error(y_test, test_pred)
test_rmse = np.sqrt(test_mse)

print(f"테스트 세트 RMSE: {test_rmse}")
테스트 세트 RMSE: 6.444231470797011
import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib.colors import LogNorm

# 히스토그램 생성 (로그 스케일 적용)
h = plt.hist2d(y_test, test_pred, bins=50, norm=LogNorm(), cmap='coolwarm')

# 컬러바 추가
plt.colorbar(h[3], label='Log Frequency')

plt.xlabel('Actual Travel Time')
plt.ylabel('Predicted Travel Time')
plt.title('Actual vs Predicted Travel Time (Log Scale)')

plt.xlim(0, 120)
plt.ylim(0, 120)
plt.plot([0, 120], [0, 120], 'r--', alpha=0.5)

plt.tight_layout()
plt.show()
../_images/9df102de62f541842040a57013ae7e28be36d475948eacdad53da0ec55c3dee5.png

3.3 Exercise#

3.3.2 앙상블 러닝 실습#

  • 여러가지 앙상블 러닝 기법 중, 최근 아마존에서 Autogluon이라는 프레임워크를 발표했습니다.

  • https://auto.gluon.ai

  • 이것을 사용해서 위 문제를 풀어봅시다.

from autogluon.tabular import TabularDataset, TabularPredictor
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[3], line 1
----> 1 from autogluon.tabular import TabularDataset, TabularPredictor

ModuleNotFoundError: No module named 'autogluon'